﻿using System;
using System.IO;
using System.IO.Ports;
using System.Threading;
using System.Text;

namespace MFDrivers.uLCD_32PT
{
    public class Display
    {
        private SerialPort _port;

        private bool _backlight;
        public bool Backlight
        {
            set
            {
                this._backlight = value;
                this.SetBacklight(value);
            }
            get
            {
                return this._backlight;
            }
        }

        public string DisplayType { get; private set; }
        public int HardwareRev { get; private set; }
        public int SoftwareRev { get; private set; }
        public int HRes { get; private set; }
        public int VRes { get; private set; }

        public Colour _backgroundColour;
        public Colour BackgroundColour
        {
            set
            {
                this.SetBackgroundColour(value);
                this._backgroundColour = value;
            }
            get
            {
                return this._backgroundColour;
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="comPort"></param>
        public Display(string comPort)
        {
            this._port = new SerialPort(comPort, 9600);
            this._port.Open();
            Thread.Sleep(500);
            this.SendCommand(new byte[] { 0x55 });
            this.LoadDeviceInfo();
        }

        public bool Clear()
        {
            return SendCommand(new byte[] { 0x45 });
        }

        public void LoadDeviceInfo()
        {
            //this.Clear();
            byte[] data = SendRead(new byte[] { 0x56, 0x00 }, 5);
            #region display type
            switch (data[0])
            {
                case 0x00:
                    this.DisplayType = "OLED";
                    break;
                case 0x01:
                    this.DisplayType = "LCD";
                    break;
                case 0x02:
                    this.DisplayType = "VGA";
                    break;
            }
            #endregion
            this.HardwareRev = data[1];
            this.SoftwareRev = data[2];

            #region HRes
            switch (data[3])
            {
                case 0x22:
                    this.HRes = 220;
                    break;
                case 0x28:
                    this.HRes = 128;
                    break;
                case 0x32:
                    this.HRes = 320;
                    break;
                case 0x24:
                    this.HRes = 240;
                    break;
                case 0x60:
                    this.HRes = 160;
                    break;
                case 0x64:
                    this.HRes = 64;
                    break;
                case 0x76:
                    this.HRes = 476;
                    break;
                case 0x96:
                    this.HRes = 96;
                    break;
                default:
                    this.HRes = 0;
                    break;
            }
            #endregion

            #region vres
            switch (data[4])
            {
                case 0x22:
                    this.VRes = 220;
                    break;
                case 0x28:
                    this.VRes = 128;
                    break;
                case 0x32:
                    this.VRes = 320;
                    break;
                case 0x24:
                    this.VRes = 240;
                    break;
                case 0x60:
                    this.VRes = 160;
                    break;
                case 0x64:
                    this.VRes = 64;
                    break;
                case 0x76:
                    this.VRes = 476;
                    break;
                case 0x96:
                    this.VRes = 96;
                    break;
                default:
                    this.VRes = 0;
                    break;
            }
            #endregion

            if (this.HRes == 0 || this.VRes == 0 || this.HardwareRev == 0 || this.SoftwareRev == 0)
                this.LoadDeviceInfo();

            return;
        }

        public bool SetBackgroundColour(Colour colour)
        {
            return this.SendCommand(new byte[] { 0x42, colour.ColourByte[0], colour.ColourByte[1] });
        }

        public bool SetBacklight(bool state)
        {
            if (!state)
                return this.SendCommand(new byte[] { 0x59, 0x00, 0x00 });
            else
                return this.SendCommand(new byte[] { 0x59, 0x00, 0x01 });
        }

        public bool SetPenSize(bool value)
        {
            return this.SendCommand(new byte[] { 0x70, (byte)((value) ? 0x00 : 0x01) });
        }

        public bool DrawCircle(Coord center, int r, Colour colour, bool filled = true)
        {
            //Set Pen Size first
            this.SetPenSize(filled);
            
            byte[] command = new byte[9];

            command[0] = 0x43; //C

            command[1] = center.XBytes[0];
            command[2] = center.XBytes[1];

            command[3] = center.YBytes[0];
            command[4] = center.YBytes[1];

            command[5] = (r > 255) ? (byte)(r >> 8) : (byte)0x00;
            command[6] = (r > 255) ? (byte)(r & 255) : (byte)r;

            command[7] = colour.ColourByte[0];
            command[8] = colour.ColourByte[1];

            return this.SendCommand(command);
        }

        public bool DrawTriangle(Coord point1, Coord point2, Coord point3, Colour colour, bool filled = true)
        {
            //Set Pen Size first
            this.SetPenSize(filled);

            byte[] command = new byte[15];

            command[0] = 0x47; //G
            #region Point 1
            //x1
            command[1] = point1.XBytes[0];
            command[2] = point1.XBytes[1];
            //y1
            command[3] = point1.YBytes[0];
            command[4] = point1.YBytes[1];
            #endregion
            #region Point 2
            //x2
            command[5] = point2.XBytes[0];
            command[6] = point2.XBytes[1];
            //y2
            command[7] = point2.YBytes[0];
            command[8] = point2.YBytes[1];
            #endregion
            #region Point 3
            //x3
            command[9] = point3.XBytes[0];
            command[10] = point3.XBytes[1];
            //y3
            command[11] = point3.YBytes[0];
            command[12] = point3.YBytes[1];
            #endregion
            //colour
            command[13] = colour.ColourByte[0];
            command[14] = colour.ColourByte[1];

            return this.SendCommand(command);
        }

        public bool DrawLine(Coord point1, Coord point2, Colour colour)
        {
            byte[] command = new byte[11];
            //0     1      2   3   4   5  6  7  8
            //draw, line, x1, y1, x2, y2, r, g, b
            command[0] = 0x4C; //L
            #region Point 1
            //x1
            command[1] = point1.XBytes[0];
            command[2] = point1.XBytes[1];
            //y1
            command[3] = point1.YBytes[0];
            command[4] = point1.YBytes[1];
            #endregion
            #region Point 2
            //x2
            command[5] = point2.XBytes[0];
            command[6] = point2.XBytes[1];
            //y2
            command[7] = point2.YBytes[0];
            command[8] = point2.YBytes[1];
            #endregion
            //colour
            command[9] = colour.ColourByte[0];
            command[10] = colour.ColourByte[1];

            return this.SendCommand(command);
        }

        public bool DrawPolygon(Coord[] points, Colour colour, bool filled = true)
        {

            throw new NotImplementedException("Don't know how to draw a polygon.");

            //Set Pen Size first
            //this.SetPenSize(filled);

            //if(points.Length < 3)
            //    throw new Exception("DrawPolygon requires at least 3 points");
            //if(points.Length > 7)
            //    throw new Exception("DrawPolygon cannot map more than 7 points");

            //byte[] command = new byte[4 + points.Length * 2];

            //command[0] = 0x67; //g

            //command[1] = (byte)(points.Length - 1);

            //int i = 2;
            //foreach (Coord point in points)
            //{
            //    command[i] = point.XBytes[0];
            //    i++;
            //    command[i] = point.XBytes[1];
            //    i++;
            //}

            ////Need to continue to use i because we don't know the lengh of command at compile time
            //command[i] = colour.ColourByte[0];
            //i++;
            //command[i] = colour.ColourByte[1];

            //return this.SendCommand(command);
        }

        public bool DrawRectangle(Coord topLeft, Coord bottomRight, Colour colour, bool filled = true)
        {
            //Set Pen Size first
            this.SetPenSize(filled);

            byte[] command = new byte[11];
            command[0] = 0x72; //r

            #region Top Left
            command[1] = topLeft.XBytes[0];
            command[2] = topLeft.XBytes[1];
            command[3] = topLeft.YBytes[0];
            command[4] = topLeft.YBytes[1];
            #endregion
            #region Bottom Right
            command[5] = bottomRight.XBytes[0];
            command[6] = bottomRight.XBytes[1];
            command[7] = bottomRight.YBytes[0];
            command[8] = bottomRight.YBytes[1];
            #endregion
            #region colour
            command[9] = colour.ColourByte[0];
            command[10] = colour.ColourByte[1];
            #endregion

            return this.SendCommand(command);
        }

        public bool DrawEllipse(Coord center, Radius r, Colour colour, bool filled = true)
        {
            //Set Pen Size first
            this.SetPenSize(filled);

            byte[] command = new byte[11];
            command[0] = 0x65; //e

            #region x & y
            command[1] = center.XBytes[0];
            command[2] = center.XBytes[1];
            command[3] = center.YBytes[0];
            command[4] = center.YBytes[1];
            #endregion
            #region x-radius
            command[5] = r.XBytes[0];
            command[6] = r.XBytes[1];
            command[7] = r.YBytes[0];
            command[8] = r.YBytes[1];
            #endregion
            #region colour
            command[9] = colour.ColourByte[0];
            command[10] = colour.ColourByte[1];
            #endregion

            return this.SendCommand(command);
        }

        public bool DrawPixel(Coord point, Colour colour)
        {
            byte[] command = new byte[7];
            command[0] = 0x50; //P

            #region x
            command[1] = point.XBytes[0];
            command[2] = point.XBytes[1];
            #endregion
            #region y
            command[3] = point.YBytes[0];
            command[4] = point.YBytes[1];
            #endregion
            #region colour
            command[5] = colour.ColourByte[0];
            command[6] = colour.ColourByte[1];
            #endregion
            return this.SendCommand(command);
        }

        public Colour ReadPixel(Coord point)
        {
            byte[] command = new byte[5];
            command[0] = 0x52; //R

            command[1] = point.XBytes[0];
            command[2] = point.XBytes[1];
            command[3] = point.YBytes[0];
            command[4] = point.YBytes[1];

            return new Colour(this.SendRead(command, 2));
        }

        public bool CopyPaste(Coord copyTopLeft, Coord copyBottomRight, Coord pasteTopLeft)
        {
            byte[] command = new byte[13];
            command[0] = 0x63; //c

            #region Source point
            command[1] = copyTopLeft.XBytes[0];
            command[2] = copyTopLeft.XBytes[1];

            command[3] = copyTopLeft.YBytes[0];
            command[4] = copyTopLeft.YBytes[1];
            #endregion

            #region destination point
            command[5] = pasteTopLeft.XBytes[0];
            command[6] = pasteTopLeft.XBytes[1];

            command[7] = pasteTopLeft.YBytes[0];
            command[8] = pasteTopLeft.YBytes[1];
            #endregion

            #region area
            //Width
            command[9] = (byte)(copyTopLeft.XBytes[0] - copyBottomRight.XBytes[0]);
            command[10] = (byte)(copyTopLeft.XBytes[1] - copyBottomRight.XBytes[1]);
            //Height
            command[11] = (byte)(copyTopLeft.YBytes[0] - copyBottomRight.YBytes[0]);
            command[12] = (byte)(copyTopLeft.YBytes[1] - copyBottomRight.YBytes[1]);
            #endregion

            return SendCommand(command);
        }

        public bool ReplaceColour(Coord topLeft, Coord bottomRight, Colour search, Colour replace)
        {
            byte[] command = new byte[13];
            command[0] = 0x6B; //k
            #region rectangle
            command[1] = topLeft.XBytes[0];
            command[2] = topLeft.XBytes[1];
            command[3] = topLeft.YBytes[0];
            command[4] = topLeft.YBytes[1];

            command[5] = bottomRight.XBytes[0];
            command[6] = bottomRight.XBytes[1];
            command[7] = bottomRight.YBytes[0];
            command[8] = bottomRight.YBytes[1];
            #endregion

            #region colours
            command[9] = search.ColourByte[0];
            command[10] = search.ColourByte[1];
            command[11] = replace.ColourByte[0];
            command[12] = replace.ColourByte[1];
            #endregion

            return this.SendCommand(command);
        }

        public bool SetFont(int font)
        {
            byte f;
            switch (font)
            {
                default:
                case 0:
                    f = 0x00;
                    break;
                case 1:
                    f = 0x01;
                    break;
                case 2:
                    f = 0x02;
                    break;
                case 3:
                    f = 0x03;
                    break;
            }
            return this.SendCommand(new byte[] { 0x26, f });
        }

        public bool DrawChar(char c, int column, int row, Colour colour)
        {
            byte[] command = new byte[6];
            command[0] = 0x54; //T
            command[1] = Encoding.UTF8.GetBytes(c.ToString())[0];
            command[2] = (byte)column;
            command[3] = (byte)row;
            command[4] = colour.ColourByte[0];
            command[5] = colour.ColourByte[1];

            return this.SendCommand(command);
        }
        
        public bool DrawChar(char c, Coord position, Colour colour, int width, int height)
        {
            byte[] command = new byte[10];
            command[0] = 0x74; //t
            command[1] = Encoding.UTF8.GetBytes(c.ToString())[0];
            #region postion
            command[2] = position.XBytes[0];
            command[3] = position.XBytes[1];
            command[4] = position.YBytes[0];
            command[5] = position.YBytes[1];
            #endregion
            command[6] = colour.ColourByte[0];
            command[7] = colour.ColourByte[1];

            command[8] = (byte)width;
            command[9] = (byte)height;

            return this.SendCommand(command);
        }

        public bool DrawString(int column, int row, string message, Colour colour, int font = 0)
        {
            
            if(message.Length > 256)
                message = message.Substring(0, 256);
            byte[] command = new byte[7 + message.Length];
            command[0] = 0x73; //s
            command[1] = (byte)column;
            command[2] = (byte)row;
            command[3] = (byte)font;
            command[4] = colour.ColourByte[0];
            command[5] = colour.ColourByte[1];
            int i = 6;
            foreach (byte b in Encoding.UTF8.GetBytes(message))
            {
                command[i] = b;
                i++;
            }
            command[i] = 0x00;

            return this.SendCommand(command);
        }
        
        public bool DrawString(Coord location, int height, int width, string message, Colour colour, int font = 0)
        {
            if (message.Length > 256)
                message = message.Substring(0, 256);
            byte[] command = new byte[11 + message.Length];
            command[0] = 0x53; //S
            command[1] = location.XBytes[0];
            command[2] = location.XBytes[1];
            command[3] = location.YBytes[0];
            command[4] = location.YBytes[1];
            command[5] = (byte)font;
            command[6] = colour.ColourByte[0];
            command[7] = colour.ColourByte[1];
            command[8] = (byte)width;
            command[9] = (byte)height;
            
            int i = 10;
            foreach (byte b in Encoding.UTF8.GetBytes(message))
            {
                command[i] = b;
                i++;
            }
            command[i] = 0x00;

            return this.SendCommand(command);
        }

        public bool DrawTextButton(bool state, Coord location, Colour buttonColour, Colour textColour, int width, int height, string message, int font = 0)
        {
            throw new NotImplementedException();
            //byte[] command = new byte[14 + message.Length];
            //command[0] = 0x62; //b
            //command[1] = (byte)((state) ? 0x01 : 0x00);
            
            //#region location
            //command[2] = location.XBytes[0];
            //command[3] = location.XBytes[1];
            //command[4] = location.YBytes[0];
            //command[5] = location.YBytes[1];
            //#endregion
            
            //command[6] = buttonColour.ColourByte[0];
            //command[7] = buttonColour.ColourByte[1];

            //command[8] = (byte)font;

            //command[9] = textColour.ColourByte[0];
            //command[10] = textColour.ColourByte[1];

            //command[11] = (byte)width;
            //command[12] = (byte)height;

            //int i = 13;
            //foreach (byte b in Encoding.UTF8.GetBytes(message))
            //{
            //    command[i] = b;
            //    i++;
            //}
            //command[i] = 0x00;

            //return this.SendCommand(command);
        }

        public bool InitSDCard()
        {
            return this.SendCommand(new byte[] { 0x40, 0x69 });
        }

        public byte[] ReadFileFromSDCard(string filename)
        {
            byte[] filenameBytes = Encoding.UTF8.GetBytes(filename);
            byte[] command = new byte[4 + filename.Length];
            command[0] = 0x40; //@
            command[1] = 0x61; //a
            command[2] = 0x04;
            int i = 3;
            foreach(byte b in filenameBytes)
            {
                command[i] = b;
                i++;
            }
            command[i] = 0x00;

            this._port.Write(command, 0, command.Length);

            byte[] file, buffer = new byte[4];

            this._port.Read(buffer, 0, buffer.Length);

            int length = 16;
            int chunks = (length / 4);

            //If there are 6 bytes then we need to collect 2 chunks
            if (chunks * 4 != length)
                chunks++;

            file = new byte[length];
            int j = 0;
            for(i = 0; i < chunks; i++ )
            {
                this._port.Write(new byte[] { 0x06 }, 0, 1); //The the last chunk
                //Read the next chunk
                this._port.Read(buffer, 0, 4);

                //Insert the chunk from the buffer to the file
                for (int k = 0; k < buffer.Length; k++, j++)
                {
                    if (buffer[k] != 0x06 && buffer[k] != 0x00)
                        file[j] = buffer[k];
                }
            }

            return new byte[0];
        }

        public bool WriteFiletoSDCard(string filename, FileMode mode, byte[] data)
        {
            throw new NotImplementedException();
        }

        public bool DeleteFileFromSDCard(string filename)
        {
            throw new NotImplementedException();
        }

        public bool ListFilesInDirectory(string path)
        {
            throw new NotImplementedException();
        }

        public bool SaveScreenShotToSDCard(Coord topLeft, int width, int height, string filename)
        {
            throw new NotImplementedException();
        }

        public bool DisplayImageFromSDCard(string filename, Coord position, byte[] address)
        {
            throw new NotImplementedException();
        }

        public bool PlayAudioFromSDCard(byte action, string filename)
        {
            throw new NotImplementedException();
        }

        public bool RunScriptFromSDCard(string filename)
        {
            throw new NotImplementedException();
        } 

        public byte[] SendRead(byte[] command, int count)
        {
            this._port.Write(command, 0, command.Length);
            byte[] buffer = new byte[count];
            Thread.Sleep(25);
            this._port.Read(buffer, 0, buffer.Length);

            return buffer;
        }

        /// <summary>
        /// Send a command which returns boolean value (writes)
        /// </summary>
        /// <param name="command">Command to be sent</param>
        /// <returns>True on 0x06, False on 0x15</returns>
        public bool SendCommand(byte[] command)
        {
            this._port.Write(command, 0, command.Length);
            byte[] buffer = new byte[1];
            while (true)
            {
                this._port.Read(buffer, 0, buffer.Length);
                if (buffer[0] == 0x06)
                    return true;
                else if (buffer[0] == 0x15)
                    return false;
            }
        }
    }
}
